﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading.Tasks;

namespace BhServer
{
    [ServiceContract(CallbackContract = typeof(IClientCallback))]
    public interface IServer
    {
        [OperationContract]
        User LogIn(string name);
        
        [OperationContract]
        void LogOut(User user);

        [OperationContract]
        void SendMessage(User from, User to, string message);

        [OperationContract]
        User[] GetUsers();

    }

    public class Server : IServer
    {
        private static Server instance = new Server();
        private Server()
        {

        }
        public static void Run()
        {

        }

        static object lockUsers = new object();

        static List<string> users = new List<string>();
        static Dictionary<Guid, User> userList = new Dictionary<Guid, User>();
        static Dictionary<Guid, IClientCallback> userCallBack = new Dictionary<Guid, IClientCallback>();

        public User LogIn(string name)
        {
            lock (lockUsers)
            {
                string newName = name;
                int i = 1;
                while (users.Contains(newName))
                {
                    newName = name + i;
                    i++;
                }
                users.Add(newName);
                User u = new User(newName);
                List<Guid> list = new List<Guid>();

                foreach (var kvp in userList)
                {
                    try { userCallBack[kvp.Value.Id].UserConnected(u); }
                    catch { list.Add(kvp.Key); }
                }
                foreach (var id in list)
                {
                    DisconnectUser(userList[id]);
                }
                userList.Add(u.Id, u);

                var callback = OperationContext.Current.GetCallbackChannel<IClientCallback>();
                if (callback != null)
                {
                    userCallBack.Add(u.Id, callback);
                }
                return u;
            }
        }
        private void DisconnectUser(User user)
        {
            userList.Remove(user.Id);
            userCallBack.Remove(user.Id);
            users.Remove(user.Name);
            List<Guid> list = new List<Guid>();

            foreach (var kvp in userCallBack)
            {
                try { kvp.Value.UserDisconnected(user); }
                catch { list.Add(kvp.Key); }
            }
            foreach (var id in list)
            {
                DisconnectUser(userList[id]);
            }
        }

        public void LogOut(User user)
        {
            lock (lockUsers)
            {
                if (userList.ContainsKey(user.Id))
                {
                    DisconnectUser(user);
                }
            }
        }
        private class NotifyMsg
        {
            public User From { get; set; }
            public User To { get; set; }
            public string Message{ get; set; }
        }
        public void SendMessage(User from, User to, string message)
        {
           NotifyMsg msg = new NotifyMsg();
           msg.From = from;
           msg.To = to;
           msg.Message = message;
           Task t = Task.Factory.StartNew(NotifyMessage, msg);
        }

        private void NotifyMessage(object obj)
        {
            lock (lockUsers)
            {
                NotifyMsg msg = obj as NotifyMsg;
                try
                {
                    if (userCallBack.ContainsKey(msg.To.Id))
                    {
                        try
                        {
                            userCallBack[msg.To.Id].NotifyMessage(msg.From, msg.Message);
                        }
                        catch
                        {
                            DisconnectUser(msg.To);
                            try
                            {
                                userCallBack[msg.From.Id].NotifyUserLeft(msg.To);
                            }
                            catch { DisconnectUser(msg.From); }
                        }
                    }
                }
                catch { }
            }
        }
        
        public User[] GetUsers()
        {
            lock (lockUsers)
            {
                return userList.Values.ToArray();
            }
        }
    }

    public interface IClientCallback
    {
        [OperationContract(IsOneWay = true)]
        void CheckConnection();

        [OperationContract(IsOneWay = true)]
        void UserDisconnected(User user);

        [OperationContract(IsOneWay = true)]
        void UserConnected(User user);

        [OperationContract(IsOneWay = true)]
        void NotifyMessage(User from, string message);

        [OperationContract(IsOneWay = true)]
        void NotifyUserLeft(User from);

    }
}
